package com.example.johnerez.myapplication;

import android.accounts.AccountsException;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.AsyncTask;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import com.example.johnerez.myapplication.DataTypes.Dish;
import com.example.johnerez.myapplication.DataTypes.Restaurant;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;

import org.json.JSONException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

public class DishesSearchActivity extends ActionBarActivity {
    private static final String DEBUG_TAG = "DEBUG_TAG";
    private String query = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(DEBUG_TAG, "onCreate()");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dishes_search);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new DishesSearchFragment())
                    .commit();
        }
        handleIntent(getIntent());

    }

    @Override
    protected void onNewIntent(Intent intent) {
        Log.d(DEBUG_TAG, "onNewIntent()");
        setIntent(intent);
        handleIntent(intent);
    }

    private void handleIntent(Intent intent) {
        Log.d(DEBUG_TAG, "handleIntent()");
        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
/*
            ActionBar bar = getActionBar();
            bar.setBackgroundDrawable(new ColorDrawable(Color.BLUE));
            bar.setDisplayShowTitleEnabled(false);
            bar.setDisplayShowTitleEnabled(true); */

            String query = intent.getStringExtra(SearchManager.QUERY);
            this.query = query;
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);

        MenuItem searchViewItem = menu.findItem(R.id.search);
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchViewItem);
        searchView.setIconifiedByDefault(true);
        initializeSearchView(this, searchView);

        return true;
    }

    private static void initializeSearchView(final ActionBarActivity context, final SearchView searchView) {
        SearchManager searchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
        searchView.setSearchableInfo(searchManager.getSearchableInfo(
                new ComponentName(context.getApplicationContext(), DishesSearchActivity.class)));
        searchView.setSearchableInfo(searchManager.getSearchableInfo(context.getComponentName()));
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                Log.d(DEBUG_TAG, "onQueryTextSubmit(" + query + ")");
                DishesSearchFragment fragment = (DishesSearchFragment) context.getSupportFragmentManager().findFragmentById(R.id.container);
                fragment.startSearch(query);

                // Hide the keyboard and take focus off the search box.
                searchView.clearFocus();

                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                Log.d(DEBUG_TAG, "onQueryTextChange(" + newText + ")");
                return false;
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
//        if (id == R.id.action_settings) {
//            return true;
//        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class DishesSearchFragment extends Fragment {

        private DishAdapter adapter;
        private GoogleApiClient mGoogleApiClient;
        private Location mLastLocation;
        private ProgressBar progressBar;
        private ListView listView;
        private List<CheckBox> checkBoxes;
        private List<TextView> checkTextBoxes;
        private LinearLayout filters;
        private View introStuff;
        private View noResultsText;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            Log.d(DEBUG_TAG, "DishesSearchFragment.onCreate()");
            super.onCreate(savedInstanceState);

            buildGoogleApiClient();
        }


        protected synchronized void buildGoogleApiClient() {
            Log.d(DEBUG_TAG, "buildGoogleApiClient()");
            mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                    .addConnectionCallbacks(connectionCallbacks)
                    .addOnConnectionFailedListener(onConnectionFailedListener)
                    .addApi(LocationServices.API)
                    .build();
            mGoogleApiClient.connect();
        }

        GoogleApiClient.ConnectionCallbacks connectionCallbacks =
                new GoogleApiClient.ConnectionCallbacks() {

                    @Override
                    public void onConnected(Bundle bundle) {
                        Log.d(DEBUG_TAG, "onConnected()");
                        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                                mGoogleApiClient);
                    }

                    @Override
                    public void onConnectionSuspended(int i) {
                        Log.d(DEBUG_TAG, "onConnectionSuspended()");

                    }
                };

        GoogleApiClient.OnConnectionFailedListener onConnectionFailedListener =
                new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) {
                        Log.d(DEBUG_TAG, "onConnectionFailed()");

                    }
                };

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            Log.d(DEBUG_TAG, "onCreateView()");
            View rootView = inflater.inflate(R.layout.fragment_dishes_search, container, false);


            listView = (ListView) rootView.findViewById(R.id.listView);
            adapter = new DishAdapter(getActivity());
            listView.setAdapter(adapter);
            noResultsText = rootView.findViewById(R.id.noResultsText);

            progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
            progressBar.setVisibility(View.GONE);

            CheckBox checkBox1 = (CheckBox) rootView.findViewById(R.id.checkBox1);
            CheckBox checkBox2 = (CheckBox) rootView.findViewById(R.id.checkBox2);
            CheckBox checkBox3 = (CheckBox) rootView.findViewById(R.id.checkBox3);
            CheckBox checkBox4 = (CheckBox) rootView.findViewById(R.id.checkBox4);
            checkBoxes = new ArrayList<CheckBox>();
            checkBoxes.add(checkBox1);
            checkBoxes.add(checkBox2);
            checkBoxes.add(checkBox3);
            checkBoxes.add(checkBox4);

            checkBox1.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    updateFilters();
                }
            });
            checkBox2.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    updateFilters();
                }
            });
            checkBox3.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    updateFilters();
                }
            });
            checkBox4.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    updateFilters();
                }
            });

            TextView checkText1 = (TextView) rootView.findViewById(R.id.checkText1);
            TextView checkText2 = (TextView) rootView.findViewById(R.id.checkText2);
            TextView checkText3 = (TextView) rootView.findViewById(R.id.checkText3);
            TextView checkText4 = (TextView) rootView.findViewById(R.id.checkText4);
            checkTextBoxes = new ArrayList<TextView>();
            checkTextBoxes.add(checkText1);
            checkTextBoxes.add(checkText2);
            checkTextBoxes.add(checkText3);
            checkTextBoxes.add(checkText4);

            filters = (LinearLayout) rootView.findViewById(R.id.filters);
            filters.setVisibility(View.GONE);

            introStuff = rootView.findViewById(R.id.introStuff);

//            SearchView searchView = (SearchView) rootView.findViewById(R.id.searchView);
//            initializeSearchView((ActionBarActivity) getActivity(), searchView);


            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position,
                                        long id) {
                    Log.d(DEBUG_TAG, "got item click");
                    Intent intent = new Intent(view.getContext(), DishesViewActivity.class);
                    DishTile dish = adapter.getItem(position);
                    intent.putExtra("DishTile", (Serializable) dish);
                    view.getContext().startActivity(intent);
                    //String message = "abc";
                    //intent.putExtra(EXTRA_MESSAGE, message);
                    //startActivity(intent);
                }
            });

            String query = ((DishesSearchActivity) getActivity()).query;
            if (!query.isEmpty()) {
                startSearch(query);
            }

            return rootView;
        }

        private void updateFilters() {
            Log.d(DEBUG_TAG, String.format("filtering %d dishes", adapter.getDishes().size()));

            List<DishTile> newShownDishes;
            Set<String> filters = new HashSet<String>();
            for (int i = 0; i < checkBoxes.size(); i++) {
                if (checkBoxes.get(i).isChecked()) {
                    if (i < checkTextBoxes.size()) {
                        String filterText = checkTextBoxes.get(i).getText().toString().toLowerCase();
                        Log.d(DEBUG_TAG, String.format("filter %d: %s", i, filterText));
                        filters.add(filterText);
                    }
                }
            }

            if (filters.isEmpty()) {
                Log.d(DEBUG_TAG, String.format("No filters"));
                newShownDishes = adapter.getDishes();
            } else {
                Set<DishTile> filteredDishTiles = new HashSet<DishTile>();

                if (adapter.getDishes() == null) {
                    return;
                }

                OuterLoop:
                for (int i = 0; i < adapter.getDishes().size(); i++) {
                    DishTile dishTile = adapter.getDishes().get(i);

                    for (String filter : filters) {
                        if (dishTile.dish.description.toLowerCase().contains(filter) ||
                                dishTile.dish.name.toLowerCase().contains(filter)) {
                            Log.d(DEBUG_TAG, String.format("found %s in %s or %s", filter, dishTile.dish.name, dishTile.dish.description));
                        } else {
                            continue OuterLoop;
                        }
                    }
                    filteredDishTiles.add(dishTile);
                }

                newShownDishes = new ArrayList<DishTile>(filteredDishTiles);
            }
            Log.d(DEBUG_TAG, String.format("now showing %d dishes", newShownDishes.size()));
            adapter.setShownDishes(newShownDishes);
            adapter.notifyDataSetInvalidated();
            adapter.notifyDataSetChanged();
        }

        private void startSearch(String query) {
            Log.d(DEBUG_TAG, "startSearch()");
            String stringUrl = null;
            try {
                stringUrl = "https://mac-and-cheese.sandbox.google.com/search?query=" + URLEncoder.encode(query, "UTF-8");
                if (mLastLocation != null) {
                    stringUrl += "&lat=" +
                            String.valueOf(mLastLocation.getLatitude()) + "&long=" + String.valueOf(mLastLocation.getLongitude());
                }
                Log.d(DEBUG_TAG, "stringUrl = " + stringUrl);

                new DownloadWebpageTask().execute(stringUrl);
                setSearchInProgressMode();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();

            }
        }

        private void setSearchInProgressMode() {
            Log.d(DEBUG_TAG, "setSearchInProgressMode()");
            progressBar.setVisibility(View.VISIBLE);
            listView.setAlpha(0.3f);
            listView.setEnabled(false);
        }

        private void goToShowingResultsMode(ResponseModel responseModel) {
            progressBar.setVisibility(View.GONE);
            if (responseModel == null) {
                Log.d(DEBUG_TAG, "No response received.");
                return;
            }
            Log.d(DEBUG_TAG, "goToShowingResultsMode()");
            adapter.setDishes(responseModel.dishTiles);
            introStuff.setVisibility(View.GONE);
            listView.setAlpha(1);
            listView.setEnabled(true);
            updateFilterCheckTextBoxes(responseModel.filters);
            filters.setVisibility(adapter.getCount() == 0 ? View.GONE : View.VISIBLE);
            noResultsText.setVisibility(adapter.getCount() == 0 ? View.VISIBLE : View.GONE);
        }

        private void updateFilterCheckTextBoxes(List<String> filters) {
            // Set the text of the filter textboxes.
            for (int i = 0; i < 4; i++) {
                if (filters.size() < i + 1) {
                    break;
                }
                checkBoxes.get(i).setChecked(false);
                checkTextBoxes.get(i).setText(filters.get(i));
            }


        }


        private DataTypes.Response parseResponse(String json) throws JSONException {
            Log.d(DEBUG_TAG, "parseResponse(" + json + ")");
            DataTypes.Response originalResponse = JsonMenuParser.parseResponse(json);

            // We must de-dupe the restaurants.
            HashMap<String, Restaurant> newRestaurants = new HashMap<String, Restaurant>();
            List<Restaurant> returnedRestaurants = originalResponse.restaurants;

            // To avoid lots of initialization logic, first go through and add a unique copy of
            // each restaurant.
            HashMap<String, Dish> deletedDishes= new HashMap<String, Dish>();
            for (Restaurant restaurant : returnedRestaurants) {
                Restaurant newRestaurant = newRestaurants.get(restaurant.name);
                if (newRestaurant == null) {
                    newRestaurants.put(restaurant.name, restaurant);
                    deletedDishes.put(restaurant.name, restaurant.matchingDishes.get(0));
                    restaurant.matchingDishes.clear();
                }
            }

            // Then, collect all the dishes for each restaurant.
            for (Restaurant restaurant : returnedRestaurants) {
                Restaurant newRestaurant = newRestaurants.get(restaurant.name);
                newRestaurant.matchingDishes.addAll(restaurant.matchingDishes);
            }
            List<Restaurant> newRestaurantList = new ArrayList<Restaurant>();
            for (Restaurant restaurant: newRestaurants.values()) {
                restaurant.matchingDishes.add(deletedDishes.get(restaurant.name));
                newRestaurantList.add(restaurant);
            }

            // Capitalize first letter of filter words.
            List<String> newFilters = new ArrayList<String>();
            for (String filter : originalResponse.filters) {
                String[] words = filter.split(" ");
                StringBuilder ret = new StringBuilder();
                for(int i = 0; i < words.length; i++) {
                    ret.append(Character.toUpperCase(words[i].charAt(0)));
                    ret.append(words[i].substring(1));
                    if(i < words.length - 1) {
                        ret.append(' ');
                    }
                }
                newFilters.add(ret.toString());
            }
            return new DataTypes.Response(newRestaurantList, newFilters);
        }

        private List<DishTile> buildMatchingDishTiles(DataTypes.Response response) {
            Log.d(DEBUG_TAG, "buildMatchingDishTiles");
            List<DishTile> dishes = new ArrayList<>();
            for (Restaurant restaurant : response.restaurants) {
                Log.d(DEBUG_TAG, restaurant.name);
                for (Dish dish : restaurant.matchingDishes) {
                    dishes.add(new DishTile(dish, restaurant));
                    Log.d(DEBUG_TAG, dish.name);
                }

            }

            rankDishes(dishes);

            return dishes;
        }

        private void rankDishes(List<DishTile> dishes) {
            if (dishes.size() == 0) {
                return;
            }

            final double maxDistanceKm = getMaxDistanceKm(dishes);

            Collections.sort(dishes, new Comparator<DishTile>() {
                @Override
                public int compare(DishTile lhs, DishTile rhs) {
                    if (rankDishTile(lhs, maxDistanceKm) < rankDishTile(rhs, maxDistanceKm)) {
                        return 1;
                    }
                    if (rankDishTile(lhs, maxDistanceKm) > rankDishTile(rhs, maxDistanceKm)) {
                        return -1;
                    }
                    return 0;
                }
            });

            for (DishTile dish : dishes) {
                Log.d(DEBUG_TAG, String.format("Distance: %f  Stars: %d  Rank: %f",
                        dish.restaurant.distanceKm, dish.restaurant.yelpRating, rankDishTile(dish, maxDistanceKm)));
            }

        }

        private double rankDishTile(DishTile dishTile, double maxDistanceKm) {
            return combineRanks(getDistanceRank(dishTile, maxDistanceKm), getStarRank(dishTile));
        }

        private double combineRanks(double distanceRank, double starRank) {
            return distanceRank + starRank;
        }

        private double getDistanceRank(DishTile dishTile, double maxDistanceKm) {
            return (1 - dishTile.restaurant.distanceKm / maxDistanceKm) * 0.5;
        }

        private double getStarRank(DishTile dishTile) {
            return dishTile.restaurant.yelpRating / 8.0;
        }

        private double getMaxDistanceKm(List<DishTile> dishes) {
            List<Double> distances = new ArrayList<>();
            for (DishTile tile : dishes) {
                distances.add(tile.restaurant.distanceKm);
            }
            return Collections.max(distances);
        }



        // Uses AsyncTask to create a task away from the main UI thread. This task takes a
        // URL string and uses it to create an HttpUrlConnection. Once the connection
        // has been established, the AsyncTask downloads the contents of the webpage as
        // an InputStream. Finally, the InputStream is converted into a string, which is
        // displayed in the UI by the AsyncTask's onPostExecute method.
        private class DownloadWebpageTask extends AsyncTask<String, Void, ResponseModel> {
            @Override
            protected ResponseModel doInBackground(String... urls) {

                // params comes from the execute() call: params[0] is the url.
                try {
                    return downloadUrlAndParseDishes(urls[0]);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (AccountsException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                return null;
            }

            // onPostExecute displays the results of the AsyncTask.
            @Override
            protected void onPostExecute(ResponseModel result) {
                goToShowingResultsMode(result);
            }

            // Given a URL, establishes an HttpUrlConnection and retrieves
            // the web page content as a InputStream, which it returns as
            // a string.
            private ResponseModel downloadUrlAndParseDishes(String myUrl) throws IOException, AccountsException, JSONException {
                InputStream is = null;

                try {
                    URL url = new URL(myUrl);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setReadTimeout(10000 /* milliseconds */);
                    conn.setConnectTimeout(15000 /* milliseconds */);
                    conn.setRequestMethod("GET");
                    conn.setDoInput(true);
                    // Starts the query
                    conn.connect();
                    int response = conn.getResponseCode();
                    Log.d(DEBUG_TAG, "The response is: " + response);
                    is = conn.getInputStream();

                    DataTypes.Response responseData = parseResponse(inputStreamToString(is));
                    List<DishTile> dishTiles = buildMatchingDishTiles(responseData);
                    List<String> filters = responseData.filters;

                    ResponseModel responseModel = new ResponseModel(dishTiles, filters);

                    // Load all the images.
                    for (DishTile dishTile : dishTiles) {
                        String imageUrl = dishTile.dish.imageUrl;
                    }

                    return responseModel;

                    // Makes sure that the InputStream is closed after the app is
                    // finished using it.
                } finally {
                    if (is != null) {
                        is.close();
                    }
                }
            }

            private String inputStreamToString(InputStream stream) throws IOException {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(stream, "UTF-8"));
                StringBuilder result = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    result.append(line);
                }
                return result.toString();
            }

        }

        private List<DishTile> mockDishes() {
            List<DishTile> dishes = new ArrayList<DishTile>();
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            dishes.add(createMockDishTile());
            return dishes;
        }

        private DishTile createMockDishTile() {
            return new DishTile(
                    createMockDish(),
                    new Restaurant("Kaylin's diner", createMockDishesList(createMockDish()), createMockDishesList(createMockDish()), "NYC",
                            "555-5555", 34.3, 32.6, 0.45, 2, 115, 1, createMockReviews()));
        }

        private Dish createMockDish() {
            return new Dish("Mac and Cheese", 14, "The best mac and cheese in the world",
                    "http://t2.gstatic.com/images?q=tbn:ANd9GcQYLM0agL4iNymXwhpj5daxkob7UXeLcgeiTCDYiulJMuiNgg", createMockReviews());
        }

        private List<Dish> createMockDishesList(Dish... mockDishes) {
            return Arrays.asList(mockDishes);
        }

        private List<DataTypes.Review> createMockReviews() {
            return new ArrayList<>();
        }
    }
}
